package cn.xiongxf.service.impl;

import cn.xiongxf.doc.CourseDoc;
import cn.xiongxf.dto.ESCourseDto;
import cn.xiongxf.mapper.HighlightResultMapper;
import cn.xiongxf.repository.ESCourseRepository;
import cn.xiongxf.result.AggPageList;
import cn.xiongxf.result.PageList;
import cn.xiongxf.service.IESCourseService;
import cn.xiongxf.vo.BucketVO;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
@Slf4j
public class ESCourseServiceImpl implements IESCourseService {
    @Autowired
    private ESCourseRepository repository;
    @Autowired
    private HighlightResultMapper resultMapper;
    @Autowired
    private ElasticsearchRestTemplate restTemplate;

    /**
     * 保存课程
     * @param doc
     */
    @Override
    public void save(CourseDoc doc) {
        // @Field(type = FieldType.Keyword)失效，手动开启映射！！！
        // 创建索引
        restTemplate.createIndex(CourseDoc.class);
        // 创建文档映射
        restTemplate.putMapping(CourseDoc.class);
        repository.save(doc);
    }

    /**
     * 删除课程
     * @param id
     */
    @Override
    public void delete(Long id) {
        repository.deleteById(id);
    }

    /**
     * ES全局搜索课程
     * @param dto
     */
    @Override
    public PageList<CourseDoc> searchCourse(ESCourseDto dto) {
        /*
        接口创建对象的4种方式：
            1.找现成子类
            2.自己创建子类
            3.找Builder创建
            4.可能有方法来创建自己
         */
        // 创建ES查询构造器
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
        // ==================================== 1、2.设置分页、排序 ======================================= \\
        // 设置分页
        builder.withPageable(PageRequest.of(dto.getPage() - 1, dto.getRows()));

        // 2.设置排序
        if (StringUtils.hasLength(dto.getSortField())){
            // 获取排序字段名
            String sortFieldName = getsortFieldName(dto.getSortField());
            // 获取排序方式
            SortOrder sortOrder = getSortOrder(dto.getSortType());
            // 设置排序字段名和排序方式
            builder.withSort(new FieldSortBuilder(sortFieldName).order(sortOrder)) ;
        }


        // ==================================== 3.设置查询条件 ======================================= \\
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();  // new BoolQueryBuilder();

        // 关键字查询通过must设置
        if (StringUtils.hasLength(dto.getKeyword())){
//            QueryBuilders.fuzzyQuery()
//            QueryBuilders.prefixQuery()
//            QueryBuilders.wildcardQuery()
            boolQueryBuilder.must(QueryBuilders.matchQuery("name", dto.getKeyword()));
        }

        // 其它查询（确切条件筛选）通过filter设置
        // 等级名
        if (StringUtils.hasLength(dto.getGradeName())){
            boolQueryBuilder.filter(QueryBuilders.termQuery("gradeName", dto.getGradeName()));
        }
        // 免费/收费
        if (StringUtils.hasLength(dto.getChargeName())){
            boolQueryBuilder.filter(QueryBuilders.termQuery("chargeName", dto.getChargeName()));
        }
        // 课程类型id
        if (null != dto.getCourseTypeId()){
            boolQueryBuilder.filter(QueryBuilders.termQuery("courseTypeId", dto.getCourseTypeId()));
        }
        // 最低价
        if (null != dto.getPriceMin()){
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(dto.getPriceMin()));
        }
        // 最高价
        if (null != dto.getPriceMax()){
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lte(dto.getPriceMax()));
        }

        // 3.设置查询条件
        builder.withQuery(boolQueryBuilder);


        // ==================================== 4.设置高亮 ======================================= \\
        // 4.设置高亮
        HighlightBuilder.Field hlField = new HighlightBuilder.Field("name");
        hlField.preTags("<font style='color:red'>").postTags("</font>");
        builder.withHighlightFields(hlField);

        // ==================================== 5.设置聚合 ======================================= \\
        // 等级聚合
        TermsAggregationBuilder gradeNameAgg = AggregationBuilders.terms("gradeNameAgg").field("gradeName");
        // 收费聚合
        TermsAggregationBuilder chargeNameAgg = AggregationBuilders.terms("chargeNameAgg").field("chargeName");
        // 5.设置聚合
        builder.addAggregation(gradeNameAgg).addAggregation(chargeNameAgg);



        // ==================================== 6.执行查询 ======================================= \\
        // 构建查询对象
        SearchQuery searchQuery = builder.build();
        // 6.执行查询
//        Page<CourseDoc> page = repository.search(searchQuery);  // 不做高亮的查询方法
        AggregatedPage<CourseDoc> page = restTemplate.queryForPage(searchQuery, CourseDoc.class, resultMapper);


        // ==================================== 7.处理聚合 ======================================= \\
        // 获取aggregations
        Aggregations aggregations = page.getAggregations();
        // Entry : "gradeTermsAgg" : {Aggregation  }, Entry :"chargeNameAgg" : {Aggregation  }
        Map<String, Aggregation> aggMap = aggregations.getAsMap();
        // 用来封装结果的Map
        HashMap<String, List<BucketVO>> aggResultMap = new HashMap<>();

        // 取出Map中的聚合结果
//        aggMap.forEach((aggName, value) -> {
//            ParsedStringTerms agg = (ParsedStringTerms) value;
//            log.info("拿到聚合：{} ========================================", aggName);
        aggMap.entrySet().forEach(entry -> {
            // "gradeTermsAgg"
            String aggName = entry.getKey();
            // {Aggregation}
            ParsedStringTerms agg = (ParsedStringTerms) entry.getValue();
            log.info("拿到聚合：{} ========================================",aggName);
            // Buckets列表
            List<? extends Terms.Bucket> buckets = agg.getBuckets();
            // 用来装bucketVO的集合
            ArrayList<BucketVO> bucketVOs = new ArrayList<>();
            // Buckets列表中的key和doc_count封装到bucketVO，添加到bucketVO的集合bucketVOs
            buckets.forEach(bucket -> {
                String key = bucket.getKeyAsString();
                long docCount = bucket.getDocCount();
                BucketVO bucketVO = new BucketVO(key, docCount);
                bucketVOs.add(bucketVO);
            });
            // 添加到结果map
            aggResultMap.put(aggName, bucketVOs);
        });

        // 返回聚合分页查询结果
        return new AggPageList<>(page.getTotalElements(), page.getContent(), aggResultMap);
    }



    // 抽取方法：获取转化后的排序字段名
    private String getsortFieldName(String sortField) {
        switch (sortField.toLowerCase()){
            case "xl": return "saleCount";
            case "xp": return "onlineTime";
            case "pl": return "commentCount";
            case "jg": return "price";
            case "rq": return "viewCount";
        }
        return "saleCount";  // 默认按销量排序
    }

    // 抽取方法：获取转化后的排序方式
    private SortOrder getSortOrder(String sortType) {
        return ! StringUtils.hasLength(sortType) || sortType.toLowerCase().equals("desc") ? SortOrder.DESC : SortOrder.ASC;
    }
}
